[@lua
   include 'java-json.org/itemstrategy.lua'
   include 'java-json.org/listitemstrategy.lua'
   include 'java-json.org/types.lua'

   -- TODO: 1) use optJSONArray/opt... for optional fields
   --       2) make unmarshalling/marshalling handle fields which can be lists
   --          of one, in which case they are not in brackets

   -- helper functions
   function isSimpleType(typedef) 
	  return (next(typedef.fields) == nil)
   end

    function isRootElement(schema, type)                                   
       for _, v in pairs(schema) do                                              
          local elemName, elemType = next(v)                                     
          if elemType == type then return true end                               
       end                                                                       
       return false;                                                             
    end  

   local function isListType(typename)
	  return (typename:match("^(list)%(.+%)") ~= nil)
   end

   local function isFieldRequired(typedef)
	  return (typedef.use and "required" == typedef.use)
   end

   local function getListType(typeName)
	  return typeName:match("list%((.+)%)")
   end
   
   local function fieldIterator(tbl)
	  local function itr(tbl, i)
		 local jsonTag, typeTbl = next(tbl, i)
		 return jsonTag, unpack(jsonTag and {next(typeTbl)} or {})
	  end
	  return itr, tbl, nil
   end

   local function makeJavaSafeName(fieldName)
	  if '$' == fieldName then
		 return 'value'
	  else
		 return fieldName:gsub('[%$,/,%-,%%,#,@,!,%^,&,*,%(,%),  ,]', '_')
	  end
   end

   local function outputGeterJavadoc(typename, memberName)
	  local str = stringBuffer:new()
	  str:append('\t/**\n')
	  str:append(
		 ('\t * Gets the value of the %s property.\n'):format(memberName)
	  )
	  str:append('\t *\n')
	  str:append('\t * @return\n')
	  str:append('\t *     possible object is\n')
	  str:append(('\t *     {@link %s}\n'):format(typename))
	  str:append('\t */\n')
	  return str:str()
   end

   local function outputSeterJavadoc(typename, memberName)
	  local str = stringBuffer:new()
	  str:append('\t/**\n')
	  str:append(
		 ('\t * Sets the value of the %sVal property.\n'):format(memberName)
	  )
	  str:append('\t *\n')
	  str:append(('\t * @param %sVal\n'):format(memberName))
	  str:append('\t *     allowed object is\n')
	  str:append(('\t *     {@link %s}\n'):format(typename))
	  str:append('\t */\n')
	  return str:str()
   end


   -- json output function
   local function objectOutput(typename, typedef)
	  local str = stringBuffer:new()
	  -- generate file and import headers
	  str:append(('/* FILE: %s.java */\n'):format(typename))
	  str:append('/* This file was generated by xsdb */\n')
	  str:append(('package %s;\n'):format(javaPKGName))
	  str:append('import java.util.List;\n')
	  str:append('import java.util.ArrayList;\n')
	  str:append('import org.json.JSONObject;\n')
	  str:append('import org.apache.commons.codec.DecoderException;\n')
	  str:append('import org.json.JSONArray;\n')
	  str:append('import org.json.JSONException;\n')
	  str:append(('import %s.Marshalable;\n'):format(javaPKGName))
	  str:append(('import %s.JSONObjectAdapter;\n'):format(javaPKGName))
	  str:append(('import %s.JSONArrayAdapter;\n\n'):format(javaPKGName))
	  str:append(('import org.apache.commons.codec.DecoderException;\n\n'))
	  -- generate class definition	  
	  str:append(
		 ('public class %s implements Marshalable {\n'):format(typename)
	  )	  
	  -- generate private members
	  for JSONFieldName, fieldTypename in fieldIterator(typedef.fields) do
		 local memberName  = makeJavaSafeName(JSONFieldName)
		 if isListType(fieldTypename) then
			local lstTypename = getListType(fieldTypename)
			str:append(
			   ListItemStrategy.declaration(
				  types[lstTypename], memberName
			   )
			)
		 else
			str:append(
			   ItemStrategy.declaration(
				  types[fieldTypename], memberName
			   )
			)
		 end
	  end
	  -- generate default constructor
	  str:append(('\n\tpublic %s() {\n\t}\n\n'):format(typename))
	  -- generate default unmarshal constructor
	  local fmt = '\tpublic %s(JSONObject jObj) throws JSONException, DecoderException {\n'
	  str:append(fmt:format(typename))
	  str:append('\t\tthis(new JSONObjectAdapter(jObj));\n')
	  str:append('\t}\n\n')
	  -- generate unmarshal constructor
	  local fmt = '\tpublic %s(JSONObjectAdapter jObj) throws JSONException, DecoderException {\n'
	  str:append(fmt:format(typename))
	  for fieldName, fieldType, fieldTypedef in fieldIterator(typedef.fields) do
		 local memberName  = makeJavaSafeName(fieldName)
		 if not isFieldRequired(fieldTypedef) then
			str:append(('\t\tif (!jObj.has("%s"))\n'):format(fieldName))
			str:append(('\t\t\t_%s = null;\n'):format(memberName))
			str:append('\t\telse\n')
			if not isListType(fieldType) then str:append('\t') end
		 end

		 if isListType(fieldType) then
			local lstType = getListType(fieldType)
			str:append(
			   ListItemStrategy.unmarshal(
				  types[lstType], memberName, fieldName
			   )
			)
		 else
			str:append(
			   ItemStrategy.unmarshal(
				  types[fieldType], memberName, fieldName
			   )
			)
		 end
	  end
	  str:append('\t}\n\n')
	  -- generate 'set'ers
	  for fieldName, fieldType, fieldTypedef in fieldIterator(typedef.fields) do
		 local memberName  = makeJavaSafeName(fieldName)
		 if isListType(fieldType) then
			local lstType  = getListType(fieldType)
			local javaType = types[lstType]
			str:append(
			   outputSeterJavadoc(
				  ('Vector<%s>'):format(javaType.typename), memberName
			   )
			)
			str:append(ListItemStrategy.seter(javaType, memberName))
		 else
			local javaType = types[fieldType]
			str:append(outputSeterJavadoc(javaType.typename, memberName))
			str:append(ItemStrategy.seter(javaType, memberName))
		 end
	  end
	  -- generate 'get'ers
	  for fieldName, fieldType, fieldTypedef in fieldIterator(typedef.fields) do
		 local memberName  = makeJavaSafeName(fieldName)
		 if isListType(fieldType) then
			local lstType  = getListType(fieldType)
			local javaType = types[lstType]
			str:append(
			   outputGeterJavadoc(
				  ('Vector<%s>'):format(javaType.typename), memberName
			   )
			)
			str:append(ListItemStrategy.geter(javaType, memberName))
		 else
			local javaType = types[fieldType]
			str:append(outputGeterJavadoc(javaType.typename, memberName))
			str:append(ItemStrategy.geter(javaType, memberName))
		 end
	  end
	  -- generate marshal funciton
	  str:append('\tpublic JSONObject marshal() throws JSONException {\n')
	  str:append(
		 '\t\tJSONObjectAdapter retObj = new JSONObjectAdapter(new JSONObject());\n'
	  )
	  for fieldName, fieldType, fieldTypedef in fieldIterator(typedef.fields) do
		 local memberName  = makeJavaSafeName(fieldName)
		 if not isFieldRequired(fieldTypedef) then
            if types[fieldType].typename == "String" then
			    str:append(
			       ('\t\tif (null != _%s && 0 < _%s.length())\n'):format(
			    	  memberName, memberName
			       )
			    )
            elseif types[fieldType].typename == "byte []" then
			    str:append(
			       ('\t\tif (null != _%s && 0 < _%s.length)\n'):format(
			    	  memberName, memberName
			       )
			    )

            elseif types[fieldType].typename == "Integer" then
			    str:append(
			       ('\t\tif (null != _%s )\n'):format(
			    	  memberName, memberName
			       )
			    )

            elseif types[fieldType].typename == "Long" then
			    str:append(
			       ('\t\tif (null != _%s )\n'):format(
			    	  memberName, memberName
			       )
			    )
            else
			    str:append(
			       ('\t\tif (null != _%s )\n'):format(
			    	  memberName, memberName
			       )
			    )
            end
			if not isListType(fieldType) then str:append('\t') end
		 end

		 if isListType(fieldType) then
			local lstType = getListType(fieldType)
			str:append(
			   ListItemStrategy.marshal(
				  types[lstType], memberName, fieldName
			   )
			)
		 else
			str:append(
			   ItemStrategy.marshal(
				  types[fieldType], memberName, fieldName
			   )
			)

		 end
	  end
	  str:append('\t\treturn retObj.getJSONObject();\n')
	  str:append('\t}\n')
	  -- add end brace
	  str:append('}\n')
	  return str:str()
   end

   function printTbl(tbl, depth)
	  depth = depth or 0
	  for k,v in pairs(tbl) do
		 if "table" == type(v) then
			if nil == next(v) then 
			   dbgPrint(string.rep("  ", depth)..k.." = {}")
			else
			   dbgPrint(string.rep("  ", depth)..k.." = {")
			   printTbl(v, depth + 1)
			   dbgPrint(string.rep("  ", depth).."}")
			end
		 else
			print(string.rep("  ", depth)..k.." = "..v)
		 end
	  end
   end

   function RandomString(length)
       length = length or 1
       if length < 1 then return nil end
       local array = {}
       for i = 1, length do
           -- ascii range for capital and lower case alphabet
           local randomNum = math.random(65, 122)
           if randomNum >= 91 and randomNum <= 96 then
                randomNum = randomNum + 10
           end
           array[i] = string.char(randomNum)
       end
       return table.concat(array)
   end        

   local function hexBinary(length)
       length = length or 1
       if length < 1 then return nil end
       local array = {}
       for i = 1, length do
           -- ascii range for capital and lower case alphabet
           local randomNum = math.random(1, 16)
           hex = { "a","b","c","d","e","f", "0", "1", "2", "3", "4", "5", "6", 
                   "7", "8", "9"}
           array[i] = hex[randomNum]
       end
       return table.concat(array)
    end

    local function generateInteger()
        local positiveOrNegative = 1
        if math.random() > 0.5 then posNegRandInt = -1 end
        if math.random() > 0.5 then positiveOrNegative = -1 end
        return tostring(positiveOrNegative*math.random(1000000))
   end

   local function generateDouble()
        local positiveOrNegative = 1
        if math.random() > 0.5 then positiveOrNegative = -1 end
        return tostring(positiveOrNegative*math.random(10000000)/1000)
   end

   local function generateBoolean()
        local trueOrFalse = 1
        if math.random() > 0.5 then trueOrFalse= -1 end
        if trueOrFalse > 0 then return "true" else return "false" end
   end

   local function generatePositiveInteger()
        return tostring(math.random(1000000))
   end

   local function generateString()
        return RandomString(8)
   end

   local function generateDate()
        return tostring(math.random(12)).."\/"..tostring(math.random(30)).."\/"..tostring(math.random(1800,2100))
   end

   local function generateAddress()
        return tostring(math.random(1000))..generateString()
    end

    local function generateTime()
        return tostring(math.random(0,2400))
    end

   local function elementOutput(typename, typedef, visitType)
      local generateData = {}
      generateData["boolean"] = generateBoolean()
      generateData["double"] = generateDouble()
      generateData["long"] = generateInteger()

      generateData["integer"] = generateInteger()
      generateData["int"] = generateInteger()
      generateData["positiveInteger"] = generatePositiveInteger()
      generateData["string"] = "'"..generateString().."'"
      generateData["date"] = "'"..generateDate().."'"
      generateData["dateTime"] = "'"..generateDate()..generateTime().."'"
      generateData["address"] = "'"..generateAddress().."'"
      generateData["time"] = generateTime()
      generateData["base64Binary"] = generateString()
      generateData["hexBinary"] = hexBinary(8)

	  local str = stringBuffer:new()
      -- length of typedef.fields
      local tableLength = 0
      local count = 1
      for _ in pairs(typedef.fields) do tableLength = tableLength + 1 end

       for tableName, tableDef in pairs(typedef.fields) do
			local nextTableName, nextTable = next(tableDef)
            str:append("'"..tableName.."':")
            if (isListType(nextTableName)) then
                if generateData[getListType(nextTableName)] == nil then
                    str:append("[{"..elementOutput(nextTableName, nextTable, visitType).."}]")
                    visitType[nextTableName] = true
                else
                    str:append( "["..generateData[getListType(nextTableName)].."]" )
                end
            else
                if not isSimpleType(nextTable) then
                    str:append ("{")
                    str:append(elementOutput(nextTableName, nextTable, visitType))
                    visitType[nextTableName] = true
                    str:append ("}")
                elseif generateData[nextTableName] == nil then
                    str:append(elementOutput(nextTableName, nextTable, visitType))
                    visitType[nextTableName] = true
                else
                    str:append( generateData[nextTableName] )
                end
            end
            if count < tableLength then str:append(",") end
            count = count + 1
        end
        return str:str()
   end

   function outputKeys(JSONSchema)
        local outputStr = stringBuffer:new()
        for k in pairs(JSONSchema) do
            outputStr:append(k)
        end
        return(outputStr:str())
   end

    function marshalUnmarshalTest(JSONSchema)
        local visitType = {}
        local outputStr = stringBuffer:new()

        local function _traverse(JSONType)

            for tagname, typetable in pairs(JSONType.fields) do
                local typename, typedef = next(typetable)
                -- if type is not simple and not already referenced
                if not (visitType[typename] or isSimpleType(typedef)) then 
                    visitType[typename] = true
                    local variableName = RandomString(5)
                    local variableName2 = RandomString(5)
                    if isRootElement( JSONType.fields, typedef) then
                        outputStr:append("JSONObject "..variableName.." = new ")
                        outputStr:append("JSONObject(".."\"")
                        outputStr:append("{")
                    end

                    outputStr:append(elementOutput(typename, typedef, visitType))
                    _traverse(typedef)

                    if isRootElement( JSONType.fields, typedef) then
                        outputStr:append("}\");\n        ")
                        outputStr:append(typename.." "..variableName2.." = new "
                            ..typename.."( "..variableName.." );".."\n        ")
                        outputStr:append("System.out.println("..variableName..
                            ".toString().equals("..variableName2..".marshal().toString() ));\n\n")
                    end
                end
            end
        end
        _traverse({fields = JSONSchema})

        return outputStr:str()
    end

   -- json iteration function
   function outputJSON(JSONSchema)
	  local visitType = {}
	  local outputStr = stringBuffer:new()
	  local function _traverse(JSONType)
		 for tagname, typetable in pairs(JSONType.fields) do
			local typename, typedef = next(typetable)
            -- try to caputure anything inside of parenthesis                
            fList = typename:match("%((.-)%)")
            if fList ~= nil then typename = fList end 
			-- if type is not simple and not already referenced
			if not (visitType[typename] or isSimpleType(typedef)) then 
			   visitType[typename] = true
			   outputStr:append(objectOutput(typename, typedef))
			   _traverse(typedef)
			end
		 end
	  end
	  _traverse({fields = JSONSchema})
	  return outputStr:str()
   end
]
